﻿using Neo4jClient;
using System;
using System.Collections.Generic;
using System.Linq;

namespace Neo4j.DataProvider
{
    /// <summary>
    /// Neo4j data provider
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class Neo4jDataProvider<T> where T : Neo4jBaseModel
    {
        IGraphClient _client = null;
        public Neo4jDataProvider(IGraphClient client)
        {
            _client = client;
        }

        /// <summary>
        /// Create node
        /// </summary>
        /// <param name="node"></param>
        /// <returns></returns>
        public T Create(T node)
        {
            if (_client != null)
            {
                var inputString = string.Format("({0}:{1}", typeof(T).Name.ToLower(), typeof(T).Name);
                inputString += " { newNode })";
                _client.Cypher
                    .Create(inputString)
                    .WithParam("newNode", node)
                    .ExecuteWithoutResults();
            }
            return node;
        }

        /// <summary>
        /// Create node if node isn't exist by property name
        /// </summary>
        /// <param name="node"></param>
        /// <param name="propertyName"></param>
        /// <returns></returns>
        public T CreateIfNotExist(T node, string propertyName)
        {
            if (_client != null)
            {
                var name=typeof(T).Name;
                var parName=name.ToLower();

                var inputString = string.Format("({0}:{1} {{ {2}: {{{3}}} }})", parName, name, propertyName, propertyName.ToLower());

                var nodeString = string.Format("{0} = {{newNode}}", parName);

                _client.Cypher
                    .Merge(inputString)
                    .OnCreate()
                    .Set(nodeString)
                    .WithParam("newNode", node)
                    .WithParam("id", node.GetType().GetProperty(propertyName).GetValue(node, null))
                    .ExecuteWithoutResults();
            }
            return node;
        }

        /// <summary>
        /// Выбрать все модели определенного типа
        /// </summary>
        /// <param name="node"></param>
        /// <returns></returns>
        public IEnumerable<T> All
        {
            get
            {
                var name = typeof(T).Name;
                var parName = name.ToLower();

                var inputString = string.Format("({0}:{1})", "node", typeof(T).Name);
                var results = _client.Cypher
                    .Match(inputString)
                    .Return(node => node.As<T>())
                    .Results;
                return results;
            }
        }

        /// <summary>
        /// Get single node
        /// </summary>
        /// <param name="id">Node id</param>
        /// <returns></returns>
        public T Single(int id)
        {
            T result = null;
            if (_client != null)
            {
                var inputString = string.Format("({0}:{1})", "node", typeof(T).Name);
                var query = _client.Cypher
                        .Match(inputString)
                        .Where((T node) => node.Id == id)
                        .Return(node => node.As<T>());
                result = query.Results.FirstOrDefault();
            }

            return result;
        }

        /// <summary>
        /// Найти элемент по значению определенного свойства
        /// </summary>
        /// <param name="propName">Наименование свойства</param>
        /// <param name="propValue">Значение свойства</param>
        /// <returns></returns>
        public T GetByPropertyValue(string propName, object propValue)
        {
            T result = null;
            if (_client != null)
            {
                var inputString = string.Format("({0}:{1} {{ {2}:'{3}' }})", "node", typeof(T).Name, propName, propValue);
                var query = _client.Cypher
                        .Match(inputString)
                        .Return(node => node.As<T>());
                result = query.Results.FirstOrDefault();
            }

            return result;
        }

        /// <summary>
        /// Update node
        /// </summary>
        /// <param name="entry"></param>
        /// <returns></returns>
        public T Update(T entry)
        {
            if (_client != null)
            {
                var inputString = string.Format("({0}:{1})", "node", typeof(T).Name);
                _client.Cypher
                    .Match(inputString)
                    .Where((T node) => node.Id == entry.Id)
                    .Set("node = {updatedNode}")
                    .WithParam("updatedNode", entry)
                    .ExecuteWithoutResults();
            }

            return entry;
        }

        /// <summary>
        /// Delete node
        /// </summary>
        /// <param name="id">Node id</param>
        public void Delete(int id)
        {
            if (_client != null)
            {
                var inputString = string.Format("({0}:{1})", "node", typeof(T).Name);
                _client.Cypher
                    .Match(inputString)
                    .Where((T node) => node.Id == id)
                    .Delete("node")
                    .ExecuteWithoutResults();
            }
        }
    }
}
